/* Filename: client.c
 * No.151-172.流媒体广播项目
 * Description: 客户端
 * Last modified: humble 2020-09-25 11:31:54
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <net/if.h>

#include <proto.h>
#include "client.h"

struct client_conf_st client_conf = {
    .rcvport = DEFAULT_RCVPORT,
    .mgroup = DEFAULE_MGROUP,
    .player_cmd = DEFAULT_PLAYERCMD,
};

static void usage(void)
{
#define USAGE "Usage:\n\
  ./client\n\
    -M, --mgroup <224.2.2.2> 指定多拨组\n\
    -P, --port <1989>        指定接收端口\n\
    -p, --player <mpg123>    指定播放器\n\
    -H, --help               显示帮助\n\
    \n"
    printf(USAGE);
}

static ssize_t writen(int fd, const void *buf, size_t count)
{
    int pos = 0, ret = 0;
    while(count > 0){
        ret = write(fd, buf + pos, count);
        if(ret < 0){
            if(errno == EINTR){ continue; }
            perror("write()");
            return -1;
        }
        count -= ret;
        pos += ret;
    }
    return pos;
}

int main(int argc, char **argv)
{
    int pd[2];
    struct sockaddr_in laddr, serveraddr, raddr;
    socklen_t serveraddr_len, raddr_len;
    int c, len, val, ret;
    pid_t pid;
    int sd;
    int index = 0;
    struct ip_mreqn mreq;
    int chosenid;
    struct msg_listentry_st *pos;
    struct option argarr[] = {
        {"port",   1, NULL, 'P'},
        {"mgroup", 1, NULL, 'M'},
        {"player", 1, NULL, 'p'},
        {"help",   0, NULL, 'H'},
        {NULL,     0, NULL, 0}
    };

    //初始化 级别：默认值，默认文件，环境变量，命令行参数
    while((c = getopt_long(argc, argv,  "P:M:p:H", argarr, &index)) > -1){
        switch(c){
            case '?': usage(); exit(1); break;
            case 'P': client_conf.rcvport = optarg; break;
            case 'M': client_conf.mgroup = optarg; break;
            case 'p': client_conf.player_cmd = optarg; break;
            case 'H': usage(); exit(0); break;
            default: abort(); break;
        }
    }

    sd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sd < 0){ perror("socket()"); exit(1); }

    //加入多拨组
    inet_pton(AF_INET, client_conf.mgroup, &mreq.imr_multiaddr);/* if 1 succeed else if(0||-1) error */
    inet_pton(AF_INET, "0.0.0.0", &mreq.imr_address);/* if 1 succeed else if(0||-1) error */
    mreq.imr_ifindex = if_nametoindex(IFNAME); //网络设备索引号
    if(setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){ perror("setsockopt()"); exit(1); }

    //IP_MULTICAST_LOOP
    //WINDOWS(设置在接收套接字上) 设为0 则控制套接字无法接收自身消息。设置为1 则控制套接字使能接收自身消息。
    //LINUX(设置在发送套按字上) 设为0 则控制套接字无法向自身发送消息。设置为1 则控制套接字可以向自身发送消息。
    val = 1;
    if(setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, sizeof(val)) < 0){ perror("setsockopt()"); exit(1); }

    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(client_conf.rcvport));
    inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr); //第3参也可写&laddr.sin_addr.s_addr
    if(bind(sd, (void *)&laddr, sizeof(laddr)) < 0){ perror("bind()"); exit(1); }

    if(pipe(pd) < 0){ perror("pipe()"); exit(1); }

    if((pid = fork()) < 0){ perror("fork()"); exit(1); }
    if(pid == 0){ //子进程：调用解码器
        close(sd);
        close(pd[1]);
        dup2(pd[0], 0);
        if(pd[0] > 0){ close(pd[0]); }
        execl("/bin/sh", "sh", "-c", client_conf.player_cmd, NULL);
        perror("execl()");
        exit(1);
    }

    //父进程：从网络收包通过管道发给子进程
    //收节目单
    struct msg_list_st *msg_list;
    if((msg_list = malloc(MSG_LIST_MAX)) == NULL){ perror("malloc()"); exit(1); }
    while(1){
        len = recvfrom(sd, msg_list, MSG_LIST_MAX, 0, (void *)&serveraddr, &serveraddr_len);
        if(len < sizeof(struct msg_list_st)){ fprintf(stderr, "message is too small.\n"); continue; }
        if(msg_list->chnid != LISTCHNNID){ fprintf(stderr, "chnid is not match.\n"); continue; }
        break;
    }
    //打印节目单并选择频道
    for(pos = msg_list->entry; (char *)pos < ((char *)msg_list) + len ; pos = (void *)(((char *)pos) + ntohs(pos->len)))
    {
        printf("chiannel %d : %s\n", pos->chnid, pos->desc);
    }
    free(msg_list);

    while(ret < 1){
        if((ret = scanf("%d", &chosenid)) != 1){ exit(1); }
    }

    //收频道包并且发送给子进程
    struct msg_channel_st *msg_channel;
    if((msg_channel = malloc(MSG_CHANNEL_MAX)) == NULL){ perror("malloc()"); exit(1); }
    while(1){
        len = recvfrom(sd, msg_channel, MSG_CHANNEL_MAX, 0, (void *)&raddr, &raddr_len);

        if(raddr.sin_addr.s_addr != serveraddr.sin_addr.s_addr || raddr.sin_port != serveraddr.sin_port){ fprintf(stderr, "Ignore:address or port not match.\n"); continue; }

        if(len < sizeof(struct msg_channel_st)){ fprintf(stderr, "Ignore:message is too small.\n"); continue; }

        if(msg_channel->chnid == chosenid){
            fprintf(stdout, "accepted msg:%d recieved.\n", msg_channel->chnid);
            if(writen(pd[1], msg_channel->data, len - sizeof(chnid_t)) < 0){ exit(1); }
        }
    }

    free(msg_channel);
    close(sd);
    return 0;
}
